#include "protocol_link.h"
#include "cmsis_os.h"
#include "stream_buffer.h"
#include "elog.h"
#include "lwip/sys.h"
#include "lwip/api.h"
#include "lwip/tcp.h"
#include "lwip/opt.h"
// #include "if_gpio.h"
#include "protocol_cmd.h"

#define LOG_TAG                             "protocol-link"
#define LINK_PORT                           55555
#define LINK_TX_BUFF_SIZE                   (13 * 1024)

#define NET_RX_STREAM_BUFF_SIZE             (1 * 1024)

static bool connected = false;

static uint8_t net_send_buff[LINK_TX_BUFF_SIZE] = { 0 };

static osThreadAttr_t net_link_attributes;

static StreamBufferHandle_t net_rx_buff_handler = NULL;

static osMutexId_t net_rx_buff_mutex_handler = NULL;
static const osMutexAttr_t net_rx_buff_mutex_attributes = {
  .name = "net_rx_buff_mutex"
};

osMessageQueueId_t net_rx_buff_len_queue_handler = NULL;
const osMessageQueueAttr_t net_rx_buff_len_queue_attributes = {
  .name = "net_rx_buff_len_queue"
};

static void protocol_link_recv_data_set(uint8_t *buf, uint32_t len);

void protocol_link_recv_data_get(uint8_t *buf, uint32_t *len, uint32_t timeout)
{
    osStatus_t ret;
    uint16_t buff_len = 0;

    *len = 0;
    if ((!net_rx_buff_len_queue_handler) || 
        (!net_rx_buff_mutex_handler) || 
        (!net_rx_buff_handler))
    {
        return;
    }

    ret = osMessageQueueGet(net_rx_buff_len_queue_handler, &buff_len, 0, timeout);
    if (ret == osOK)
    {
        osMutexAcquire(net_rx_buff_mutex_handler, timeout);
        *len = xStreamBufferReceive(net_rx_buff_handler, buf, buff_len, 0);
        osMutexRelease(net_rx_buff_mutex_handler);
    }
}

static void protocol_link_recv_data_set(uint8_t *buf, uint32_t len)
{
    if ((!net_rx_buff_len_queue_handler) || 
        (!net_rx_buff_mutex_handler) || 
        (!net_rx_buff_handler))
    {
        return;
    }

    if (len >= NET_RX_STREAM_BUFF_SIZE)
    {
        len = NET_RX_STREAM_BUFF_SIZE;
    }

    osMutexAcquire(net_rx_buff_mutex_handler, osWaitForever);
    xStreamBufferSend(net_rx_buff_handler, buf, len, 0);
    osMutexRelease(net_rx_buff_mutex_handler);

    osMessageQueuePut(net_rx_buff_len_queue_handler, &len, 0, 0);
}

static err_t net_tx_handle(const struct netconn *conn)
{
    err_t err;
    uint32_t len = 0;

    protocol_cmd_send_data_encode(net_send_buff, &len);
    if (len)
    {
        // if_gpio_set_high(_if_gpio_test2_pin);
        err = netconn_write(conn, net_send_buff, len, NETCONN_COPY);
        // if_gpio_set_low(_if_gpio_test2_pin);
        if (err == ERR_OK)
        {
            // log_i("netconn send ok, len: %d \r\n", len);
        }
        else
        {
            connected = false;
            log_e("netconn write err: %d! \r\n", err);
        }
    }

    return err;
}

static err_t net_rx_handle(const struct netconn *conn)
{
    err_t err;
    void *data;
    uint16_t len = 0, tatol_len = 0;
    struct netbuf *buf;

    if ((err = netconn_recv(conn, &buf)) == ERR_OK)
    {
        do
        {
            len = 0;
            netbuf_data(buf, &data, &len);
            if (len)
            {
                /* 接收到的数据放到streambuffer中 */
                osMutexAcquire(net_rx_buff_mutex_handler, osWaitForever);
                xStreamBufferSend(net_rx_buff_handler, (uint8_t *)data, len, 0);
                osMutexRelease(net_rx_buff_mutex_handler);

                tatol_len += len;
            }
        }
        while (netbuf_next(buf) >= 0);

        /* 接收到的数据放到streambuffer中 */
        osMessageQueuePut(net_rx_buff_len_queue_handler, &tatol_len, 0, 0);
        netbuf_delete(buf);
    }
    
    return err;
}

static void net_link_task(void *argument)
{
    err_t err;
    struct netconn *conn, *newconn;

    LWIP_UNUSED_ARG(argument);

    conn = netconn_new(NETCONN_TCP);
    netconn_bind(conn, IP_ADDR_ANY, LINK_PORT);
    LWIP_ERROR("tcpecho: invalid conn", (conn != NULL), return;);

    /* Tell connection to go into listening mode. */
    netconn_listen(conn);

    while (1)
    {
        static uint32_t test_tick = 0;
        test_tick++;
        if (test_tick > 1000)
        {
            test_tick = 0;
            log_d("net_link loop \r\n");
        }
        osDelay(1);

        /* Grab new connection. */
        err = netconn_accept(conn, &newconn);
        if (newconn)
        {
            log_i("accepted new connection ip: %x port: %d \r\n", newconn->pcb.tcp->remote_ip.addr, newconn->pcb.tcp->remote_port);

            // 设置为非阻塞模式
            // netconn_set_nonblocking(newconn, 1);
            netconn_set_recvtimeout(newconn, 5); // 设置接收超时

            /* Process the new connection. */
            if (err == ERR_OK)
            {
                connected = true;
                newconn->pcb.tcp->so_options |= SOF_KEEPALIVE;
                while (connected == true)
                {
                    /* 发送处理 */
                    net_tx_handle(newconn);

                    /* 接收处理 */
                    err = net_rx_handle(newconn);
                    if(err != ERR_OK && err != ERR_TIMEOUT)
                    {
                        log_w("net_rx_handle err: %d \r\n", err);
                    }

                    if(err == ERR_CLSD || err == ERR_CONN || err == ERR_ABRT)
                    {
                        connected = false;
                        break;
                    }
                    osDelay(1);
                }
                netconn_close(newconn);
                netconn_delete(newconn);
                log_i("close connect \r\n");
            }
        }
        osDelay(10);
    }
}

void protocol_link_down(void)
{
    connected = false;
}

void protocol_link_init(void)
{
    net_rx_buff_mutex_handler = osMutexNew(&net_rx_buff_mutex_attributes);

    net_rx_buff_len_queue_handler = osMessageQueueNew(10, sizeof(uint32_t), &net_rx_buff_len_queue_attributes);

    net_rx_buff_handler = xStreamBufferCreate(NET_RX_STREAM_BUFF_SIZE, 1);

    /* 创建网络连接任务 */
    memset(&net_link_attributes, 0x00, sizeof(osThreadAttr_t));
    net_link_attributes.name = "net_link_task";
    net_link_attributes.stack_size = 1024 * 4;
    net_link_attributes.priority = osPriorityNormal;
    osThreadNew(net_link_task, NULL, &net_link_attributes);
}
